package br.ufc.storm.Control;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;

import org.apache.commons.io.FileUtils;
import org.jdom2.Attribute;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;

import br.ufc.storm.jaxb.AbstractComponentType;
import br.ufc.storm.jaxb.ContextArgumentType;
import br.ufc.storm.jaxb.ContextContract;
import br.ufc.storm.jaxb.ContextParameterType;
import br.ufc.storm.jaxb.InnerComponentType;
import br.ufc.storm.jaxb.InstantiationTypeType;
import br.ufc.storm.jaxb.ObjectFactory;

public class XMLHandler {
	static DocumentBuilderFactory dbf;
	static DocumentBuilder db = null;
	static Document doc = null;
	static URL url;

	/**
	 * This method open a xml file
	 * @param file XML file URL or content
	 * @return XML document opened
	 */
	public static Document openXMLFile(String file){

		dbf = DocumentBuilderFactory.newInstance();
		try {
			db = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e1) {
			e1.printStackTrace();
		}
		InputSource inStream = new InputSource(); 
		try {
			url = new URL(file);
			BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
			StringBuilder out = new StringBuilder();
			String newLine = System.getProperty("line.separator");
			String line;
			while ((line = in.readLine()) != null) {
				out.append(line);
				out.append(newLine);
			}
			file = out.toString();
			in.close();
		} catch (MalformedURLException e) {
			//This exception is purposely left in blank, because always when enter in this method, 
			//the file name can be a url or a file content as string
		} catch (IOException e) {
			e.printStackTrace();
		}
		inStream.setCharacterStream(new StringReader(file)); 
		try {
			doc = db.parse(inStream);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return doc;
	}

	/**
	 * This method open a xml file
	 * @param file XML file URL or content
	 * @return XML document opened
	 */
	public static File openXMLFileNew(String file){
		InputSource inStream = new InputSource(); 
		try {
			url = new URL(file);
			BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
			StringBuilder out = new StringBuilder();
			String newLine = System.getProperty("line.separator");
			String line;
			while ((line = in.readLine()) != null) {
				out.append(line);
				out.append(newLine);
			}
			file = out.toString();
			System.out.println(file);
			in.close();
		} catch (MalformedURLException e) {
			//This exception is purposely left in blank, because always when enter in this method, 
			//the file name can be a url or a file content as string
		} catch (IOException e) {
			e.printStackTrace();
		}
		inStream.setCharacterStream(new StringReader(file)); 
		File f = new File("out.go");
		try {
			FileUtils.writeStringToFile(f, file);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return f;
	}

	/**
	 * This method adds a context parameter from a XML file
	 * @param file XML file
	 * @return True if non exception occurs
	 */

	/*public static AbstractComponentType generateContextContractFromXML(File file){

		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return ac;*/
		
		/*
		Document doc = XMLHandler.openXMLFile(file);
		Element raiz = doc.getDocumentElement();
		NodeList listComponents = raiz.getElementsByTagName("abstract_component");
		Element component = (Element) listComponents.item(0);
		String name = component.getAttribute("name");
		String supertype = component.getAttribute("supertype");		
		String parent = component.getAttribute("parent");
		String kind = component.getAttribute("kind");
		AbstractComponentType ac = generateAbstractComponentType(name, DBHandler.getAbstractComponentID(supertype), DBHandler.getAbstractComponentID(parent), kind);
		NodeList listOfContextParameters = component.getElementsByTagName("context_parameter");
		for(int i = 1; i < listOfContextParameters.getLength(); i++){
			ac.addParam(XMLHandler.generateContextParameter((Element)listOfContextParameters.item(i)));
		}
		NodeList listOfInnerComponent = component.getElementsByTagName("inner_component");
		for(int i = 1; i < listOfInnerComponent.getLength(); i++){
			ac.addInnerComponent(XMLHandler.generateInnerComponent((Element)listOfContextParameters.item(i)));
		}
		NodeList listOfAbstractUnits = component.getElementsByTagName("abstract_unit");
		for(int i = 1; i < listOfAbstractUnits.getLength(); i++){
			ac.addAbstractUnit(XMLHandler.generateAbstractUnit((Element)listOfContextParameters.item(i)));
		}
		return null;// new ContextContract(ac);
	}*/

	/*public static br.ufc.storm.jaxb.ContextContract generateContextContractFromXML(String file){
		Document doc = XMLHandler.openXMLFile(file);
		Element raiz = doc.getDocumentElement();
		NodeList listComponents = raiz.getElementsByTagName("abstract_component");
		Element component = (Element) listComponents.item(0);
		String name = component.getAttribute("name");
		String supertype = component.getAttribute("supertype");		
		String parent = component.getAttribute("parent");
		String kind = component.getAttribute("kind");
		AbstractComponent ac = new AbstractComponent(name, DBHandler.getAbstractComponentID(supertype), DBHandler.getAbstractComponentID(parent), kind);
		NodeList listOfContextParameters = component.getElementsByTagName("context_parameter");
		for(int i = 1; i < listOfContextParameters.getLength(); i++){
			ac.addParam(XMLHandler.generateContextParameter((Element)listOfContextParameters.item(i)));
		}
		NodeList listOfInnerComponent = component.getElementsByTagName("inner_component");
		for(int i = 1; i < listOfInnerComponent.getLength(); i++){
			ac.addInnerComponent(XMLHandler.generateInnerComponent((Element)listOfContextParameters.item(i)));
		}
		NodeList listOfAbstractUnits = component.getElementsByTagName("abstract_unit");
		for(int i = 1; i < listOfAbstractUnits.getLength(); i++){
			ac.addAbstractUnit(XMLHandler.generateAbstractUnit((Element)listOfContextParameters.item(i)));
		}
		return null;// new ContextContract(ac);
	}*/
	/**
	 * Resolve a contract
	 * @param file
	 * @return
	 */
	public static List<ContextContract> resolve(File file){
		ArrayList<AbstractComponentType> list = new ArrayList<AbstractComponentType>();
		AbstractComponentType abscom;
		ContextContract cc = new ContextContract();
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			cc.setAbstractComponent(element.getValue());
		} catch (JAXBException e) {
			e.printStackTrace();
		}

		return DBHandler.resolve(cc);
	}



	/**
	 * Resolve a contract
	 * @param file
	 * @return
	 */
	public static String resolveInverse(AbstractComponentType abs){
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.lia.storm.jaxb");
			Marshaller marshaller = context.createMarshaller();
			JAXBElement<AbstractComponentType> element = new ObjectFactory().createAbstractComponent(abs);
			marshaller.marshal(element, System.out);
		} catch (JAXBException e) {
			e.printStackTrace();
		}

		return null;
	}

	public static AbstractComponentType XMLtoJava(String file){

		try {
			JAXBContext jaxbContext = JAXBContext.newInstance("br.ufc.lia.storm.jaxb");
			Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
			JAXBElement<AbstractComponentType> customer = (JAXBElement<AbstractComponentType>) jaxbUnmarshaller.unmarshal(new File(file));
			return customer.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return null;		
	}

	public static AbstractComponentType generateContextParameter(File file){
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return ac;
	}
	

	/**
	 * This method adds an abstract component from a xml file
	 * @param file XML file
	 * @return True if non exception occurs
	 */
	public static boolean addAbstractComponentFromXML(File file){
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return false;
		}
		ac.getSupertype().setIdAc(DBHandler.getAbstractComponentID(ac.getSupertype().getName()));
		ac.getParent().setIdAc(DBHandler.getAbstractComponentID(ac.getParent().getName()));
		DBHandler.addAbstractComponent(ac);
		return true;
	}

	/**
	 * This method generates a XML file with an abstract component
	 * @param ac_name Abstract component name
	 * @return XML file content with abstract component values
	 */

	public static String getAbstractComponent(String ac_name){
		AbstractComponentType abscom = null;
		abscom = DBHandler.getAbstractComponent(ac_name);
		JAXBContext context;
		java.io.StringWriter sw = new StringWriter();
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Marshaller marshaller = context.createMarshaller();
			//marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
			JAXBElement<AbstractComponentType> element = new ObjectFactory().createAbstractComponent(abscom);
			marshaller.marshal(element, sw);
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return sw.toString();
	}

	/**
	 * This method adds a context parameter from a XML file
	 * @param file XML file
	 * @return True if non exception occurs
	 */

	public static boolean addContextParameterFromXML(File file){
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return false;
		}
		ContextParameterType cp = ac.getContextParameter().get(0);
		DBHandler.addContextParameter(cp.getName(), cp.getBound().getName(), ac.getName(), cp.getContextVariable().getCcName());
		return true;
	}


	/**
	 * This method generates a XML file with all context parameters owned by a given abstract component name 
	 * @param ac_name Abstract component name
	 * @return XML file content with context parameters values
	 */

	public static String getContextParameters(String ac_name){
		AbstractComponentType ac = new AbstractComponentType();
		ac.setIdAc(DBHandler.getAbstractComponentID(ac_name));
		List<ContextParameterType> cp = DBHandler.getContextParameter(ac.getIdAc());
		List<ContextParameterType> aux = ac.getContextParameter();
		aux = cp;
		JAXBContext context;
		java.io.StringWriter sw = new StringWriter();
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Marshaller marshaller = context.createMarshaller();
			//marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
			JAXBElement<AbstractComponentType> element = new ObjectFactory().createAbstractComponent(ac);
			marshaller.marshal(element, sw);
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return sw.toString();
	}

	/**
	 * Method for add an instantiation type
	 * @param file XML file 
	 * @return Returns True if instantiation type was correctly added.
	 */

	public static int addInstantiationTypeFromXML(File file){
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return -1;
		}
		ContextContract it = ac.getContextParameter().get(0).getContextVariable();
		return DBHandler.addInstantiationType(it.getCcName(), ac.getName());
		/*return DBHandler.getInstantiationTypeID(it.getItName());*/
		
		
		
		
/*		
		String name;
		String ac_name;
		Document doc = XMLHandler.openXMLFile(file);
		Element raiz = doc.getDocumentElement();
		NodeList listComponents = raiz.getElementsByTagName("instantiation_type");
		Element component = (Element) listComponents.item(0);
		name = component.getAttribute("name");		
		ac_name = component.getAttribute("ac_name");
		DBHandler.addInstantiationType(name, ac_name);
		return DBHandler.getInstantiationTypeID(name);*/
	}

	/**
	 * This method adds a platform profile to components library
	 * @param cmp XML file content 
	 * @return True if the addiction was well successfully
	 */

	public static boolean addProfileFromXML(String cmp) {
		/*Document doc = XMLHandler.openXMLFile(cmp);
		Element raiz = doc.getDocumentElement();
		NodeList listComponents = raiz.getElementsByTagName("abstract_component");
		Element component = (Element) listComponents.item(0);
		String name = component.getAttribute("name");		
		String supertype = component.getAttribute("supertype");		
		String parent = component.getAttribute("parent");
		String kind = "Profile";
		AbstractComponent ac = new AbstractComponent(name, DBHandler.getAbstractComponentID(supertype), DBHandler.getAbstractComponentID(parent), kind);
		//Descomentar	DBHandler.addAbstractComponent(ac);
*/		return true;
	}

	/**
	 * This method adds a context argument to an abstract components 
	 * @param file XML file content 
	 * @return True if the addiction was well successfully
	 */

	
	/**
	 * This method adds an inner component
	 * @param cmp XML file content
	 * @return True if the addiction was well successfully
	 */

	public static boolean addInnerComponentFromXML(File file) {
		
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return false;
		}
		DBHandler.addInnerComponent(ac.getInnerComponent().get(0).getName(), ac.getInnerComponent().get(0).getName(), ac.getName());
		return true;
	}

	public static int addAbstractUnitFromXML(File file) {
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return -1;
		}
		String name = ac.getAbstractUnit().get(0).getAuName();
		String concrete_component_name = ac.getAbstractUnit().get(0).getAbstractComponentName();
		
		return DBHandler.addAbstractUnit(name, concrete_component_name);
	}

	/**
	 * This method adds an concrete unit
	 * @param cmp XML file content
	 * @return True if the addiction was well successfully
	 */

	public static int addUnitFromXML(File file) {
		AbstractComponentType ac = null;
		JAXBContext context;
		try {
			context = JAXBContext.newInstance("br.ufc.storm.jaxb");
			Unmarshaller unmarshaller = context.createUnmarshaller();
			JAXBElement<AbstractComponentType> element = (JAXBElement<AbstractComponentType>) unmarshaller.unmarshal(file);
			ac = element.getValue();
		} catch (JAXBException e) {
			e.printStackTrace();
			return -1;
		}
		
		//return DBHandler.addConcreteUnit(, DBHandler.getAbstractUnitID(name)+"");
		
		return -1;
		/*
		String name;
		String concrete_component_id;
		Document doc = XMLHandler.openXMLFileNew(file);
		Element raiz = doc.getDocumentElement();
		NodeList listUnits = raiz.getElementsByTagName("concrete_unit");
		Element component = (Element) listUnits.item(0);
		name = component.getAttribute("au_name");		
		concrete_component_id = component.getAttribute("concrete_component_id");
		DBHandler.addConcreteUnit(concrete_component_id, DBHandler.getAbstractUnitID(name)+"");//Corrigir
		return DBHandler.getConcreteUnitID(name); //Corrigir
*/	}

	public static boolean addUnitFile(String xml, byte [] data){
		String filename;
		String extension;
		String folder;
		String build_cfg;
		String version;
		String filetype;
		String uid;
		Document doc = XMLHandler.openXMLFile(xml);
		Element raiz = doc.getDocumentElement();
		NodeList listUnits = raiz.getElementsByTagName("unit_file");
		Element component = (Element) listUnits.item(0);
		filename = component.getAttribute("name");		
		uid = component.getAttribute("u_id");
		extension = component.getAttribute("extension");
		folder = component.getAttribute("folder");
		build_cfg = component.getAttribute("build_cfg");
		version = component.getAttribute("version");
		filetype = component.getAttribute("file_type");

		if(DBHandler.addConcreteUnitFile(filename, uid, extension, folder, build_cfg, version, filetype)){
			if(FileHandler.addFile(data, uid+"."+filename+"."+extension)){
				return true;
			}else{
				//REMOVER
				return false;
			}
		}
		return false;

	}


	/**
	 * This method gets a list of all abstract components
	 * @return XML file content
	 */

	public static String listComponent(){
		List<AbstractComponentType> abscom = null;
		abscom = DBHandler.listComponent();
		org.jdom2.Element cp = new org.jdom2.Element("root");
		org.jdom2.Document doc = new org.jdom2.Document(cp);
		for(int i = 0; i < abscom.size(); i++){ 
			org.jdom2.Element contextParameter = new org.jdom2.Element("abstract_component");
			contextParameter.setAttribute(new Attribute("ac_id", abscom.get(i).getIdAc()+""));
			contextParameter.setAttribute(new Attribute("name", abscom.get(i).getName()));
			contextParameter.setAttribute(new Attribute("supertype", abscom.get(i).getSupertype().getName()));
			contextParameter.setAttribute(new Attribute("parent", DBHandler.getAbstractComponentName(abscom.get(i).getParent().getIdAc())));
			contextParameter.setAttribute(new Attribute("kind", abscom.get(i).getKind()));
			doc.getRootElement().addContent(contextParameter);
		}
		XMLOutputter xmlOutput = new XMLOutputter();
		xmlOutput.setFormat(Format.getPrettyFormat());
		return xmlOutput.outputString((org.jdom2.Document) doc);
	}
	public static AbstractComponentType generateAbstractComponentType(int ac_id, String name, int supertype, int parent, String kind){
		AbstractComponentType ac = new AbstractComponentType();
		ac.setIdAc(ac_id);
		ac.setName(name);
		ac.setSupertype(new AbstractComponentType());
		ac.getSupertype().setIdAc(supertype);
		ac.setParent(new AbstractComponentType());
		ac.getParent().setIdAc(parent);
		ac.setKind(kind+"");
		return ac;		
	}
	
	public static AbstractComponentType generateAbstractComponentType(String name, int supertype, int parent, String kind){
		AbstractComponentType ac = new AbstractComponentType();
		ac.setName(name);
		ac.setSupertype(new AbstractComponentType());
		ac.getSupertype().setIdAc(supertype);
		ac.setParent(new AbstractComponentType());
		ac.getParent().setIdAc(parent);
		ac.setKind(kind+"");
		return ac;		
	}
	
	
	
	
	
	
	

}